﻿using System;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Text;
using Cloak.Globalization;

namespace Cloak.Reflection
{
	/// <summary>
	/// Strongly-typed reflection using <see cref="System.Action"/> and <see cref="System.Func{T}"/> delegates
	/// and the <see cref="System.Linq.Expressions"/> namespace
	/// </summary>
	public static class Reflect
	{
		#region Action
		/// <summary>
		/// Gets the method encapsulated by the specified delegate
		/// </summary>
		public static MethodInfo Action(Action action)
		{
			Contract.Requires(action != null);

			return GetMethod(action);
		}

		/// <summary>
		/// Gets the method encapsulated by the specified delegate
		/// </summary>
		public static MethodInfo Action<T>(Action<T> action)
		{
			Contract.Requires(action != null);

			return GetMethod(action);
		}

		/// <summary>
		/// Gets the method encapsulated by the specified delegate
		/// </summary>
		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "3", Justification = "Matches Action type arguments")]
		public static MethodInfo Action<T1, T2>(Action<T1, T2> action)
		{
			Contract.Requires(action != null);

			return GetMethod(action);
		}

		/// <summary>
		/// Gets the method encapsulated by the specified delegate
		/// </summary>
		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "3", Justification = "Matches Action type arguments")]
		public static MethodInfo Action<T1, T2, T3>(Action<T1, T2, T3> action)
		{
			Contract.Requires(action != null);

			return GetMethod(action);
		}

		/// <summary>
		/// Gets the method encapsulated by the specified delegate
		/// </summary>
		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "3", Justification = "Matches Action type arguments")]
		public static MethodInfo Action<T1, T2, T3, T4>(Action<T1, T2, T3, T4> action)
		{
			Contract.Requires(action != null);

			return GetMethod(action);
		}
		#endregion

		#region Func
		/// <summary>
		/// Gets the method encapsulated by the specified delegate
		/// </summary>
		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "3", Justification = "Matches Action type arguments")]
		public static MethodInfo Func<TResult>(Func<TResult> func)
		{
			Contract.Requires(func != null);

			return GetMethod(func);
		}

		/// <summary>
		/// Gets the method encapsulated by the specified delegate
		/// </summary>
		public static MethodInfo Func<T, TResult>(Func<T, TResult> func)
		{
			Contract.Requires(func != null);

			return GetMethod(func);
		}

		/// <summary>
		/// Gets the method encapsulated by the specified delegate
		/// </summary>
		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "3", Justification = "Matches Action type arguments")]
		public static MethodInfo Func<T1, T2, TResult>(Func<T1, T2, TResult> func)
		{
			Contract.Requires(func != null);

			return GetMethod(func);
		}

		/// <summary>
		/// Gets the method encapsulated by the specified delegate
		/// </summary>
		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "3", Justification = "Matches Action type arguments")]
		public static MethodInfo Func<T1, T2, T3, TResult>(Func<T1, T2, T3, TResult> func)
		{
			Contract.Requires(func != null);

			return GetMethod(func);
		}

		/// <summary>
		/// Gets the method encapsulated by the specified delegate
		/// </summary>
		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "3", Justification = "Matches Action type arguments")]
		public static MethodInfo Func<T1, T2, T3, T4, TResult>(Func<T1, T2, T3, T4, TResult> func)
		{
			Contract.Requires(func != null);

			return GetMethod(func);
		}
		#endregion

		#region Members
		/// <summary>
		/// Gets the property or field accessed in the specified lambda expression
		/// </summary>
		public static MemberInfo Member<TResult>(Expression<Func<TResult>> expression)
		{
			Contract.Requires(expression != null);

			return GetMember(expression);
		}

		/// <summary>
		/// Gets the property or field accessed in the specified lambda expression
		/// </summary>
		public static MemberInfo Member<T, TResult>(Expression<Func<T, TResult>> expression)
		{
			Contract.Requires(expression != null);

			return GetMember(expression);
		}

		/// <summary>
		/// Gets the property accessed in the specified lambda expression
		/// </summary>
		public static PropertyInfo Property<TResult>(Expression<Func<TResult>> expression)
		{
			Contract.Requires(expression != null);

			return GetMember<PropertyInfo>(expression);
		}

		/// <summary>
		/// Gets the property accessed in the specified lambda expression
		/// </summary>
		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters", Justification = "Type inference")]
		public static PropertyInfo Property<T, TResult>(Expression<Func<T, TResult>> expression)
		{
			Contract.Requires(expression != null);

			return GetMember<PropertyInfo>(expression);
		}

		/// <summary>
		/// Gets the field accessed in the specified lambda expression
		/// </summary>
		public static FieldInfo Field<TResult>(Expression<Func<TResult>> expression)
		{
			Contract.Requires(expression != null);

			return GetMember<FieldInfo>(expression);
		}

		/// <summary>
		/// Gets the field accessed in the specified lambda expression
		/// </summary>
		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters", Justification = "Type inference")]
		public static FieldInfo Field<T, TResult>(Expression<Func<T, TResult>> expression)
		{
			Contract.Requires(expression != null);

			return GetMember<FieldInfo>(expression);
		}
		#endregion

		#region Constructor
		/// <summary>
		/// Gets the constructor invoked in the specified lambda expression
		/// </summary>
		public static ConstructorInfo Constructor<TResult>(Expression<Func<TResult>> expression)
		{
			Contract.Requires(expression != null);

			return GetConstructor(expression);
		}

		/// <summary>
		/// Gets the constructor invoked in the specified lambda expression
		/// </summary>
		public static ConstructorInfo Constructor<T, TResult>(Expression<Func<T, TResult>> expression)
		{
			Contract.Requires(expression != null);

			return GetConstructor(expression);
		}

		/// <summary>
		/// Gets the constructor invoked in the specified lambda expression
		/// </summary>
		public static ConstructorInfo Constructor<T1, T2, TResult>(Expression<Func<T1, T2, TResult>> expression)
		{
			Contract.Requires(expression != null);

			return GetConstructor(expression);
		}

		/// <summary>
		/// Gets the constructor invoked in the specified lambda expression
		/// </summary>
		public static ConstructorInfo Constructor<T1, T2, T3, TResult>(Expression<Func<T1, T2, T3, TResult>> expression)
		{
			Contract.Requires(expression != null);

			return GetConstructor(expression);
		}

		/// <summary>
		/// Gets the constructor invoked in the specified lambda expression
		/// </summary>
		public static ConstructorInfo Constructor<T1, T2, T3, T4, TResult>(Expression<Func<T1, T2, T3, T4, TResult>> expression)
		{
			Contract.Requires(expression != null);

			return GetConstructor(expression);
		}
		#endregion

		private static TInfo GetInfo<TBody, TInfo>(
			LambdaExpression expression,
			Func<TBody, TInfo> getInfo)
			where TBody : Expression
			where TInfo : MemberInfo
		{
			var body = expression.Body as TBody;

			if(body == null)
			{
				throw new ArgumentException(Resources.InvalidLambdaExpressionBody.FormatInvariant(typeof(TBody), expression));
			}

			return getInfo(body);
		}

		private static MethodInfo GetMethod(Delegate wrapper)
		{
			return wrapper.Method;
		}

		private static MethodInfo GetMethod(LambdaExpression expression)
		{
			return GetInfo(expression, (MethodCallExpression call) => call.Method);
		}

		private static MemberInfo GetMember(LambdaExpression expression)
		{
			return GetInfo(expression, (MemberExpression access) => access.Member);
		}

		private static TInfo GetMember<TInfo>(LambdaExpression expression) where TInfo : MemberInfo
		{
			var info = GetMember(expression) as TInfo;

			if(info == null)
			{
				throw new ArgumentException(Resources.InvalidLambdaExpressionBody.FormatInvariant(typeof(TInfo), expression));
			}

			return info;
		}

		private static ConstructorInfo GetConstructor(LambdaExpression expression)
		{
			return GetInfo(expression, (NewExpression create) => create.Constructor);
		}
	}
}